/*
 * Copyright (c) 2007 acmesystems.it - john@acmesystems.it
 * Copyright (c) 2008 crisos.org - info@crisos.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA
 *
 * Feedback, Bugs...  john@acmesystems.it
 *
 */


#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/genhd.h>
#include <linux/errno.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/fasttimer.h>
#include <asm/delay.h>
#include <asm/etraxgpio.h>

#include "font.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("K. John '2B|!2B' Crispin");
MODULE_DESCRIPTION("SSD1303 GLCD controller FOX");

#define DEV_NAME 		"lcd"
#define DEV_MAJOR 		197
#define DRIVER_NAME		"GLCD"

#define PIN_SD_RST		((unsigned int)1 << 2)	//OG2
//FIXME The og3 is also used as CS on mmc driver, anyway I belive that mmc driver can work also without it
#define PIN_SD_RW		((unsigned int)1 << 3)	//OG3
#define PIN_SD_E		((unsigned int)1 << 5)	//OG5

#ifdef OLD_IF
 #warning this configuration is not compatible with mmc driver
 #define PIN_SD_CS		((unsigned int)1 << 1)	//OG1
 #define PIN_SD_DI		((unsigned int)1 << 4)	//OG4
#else
 #define PIN_SD_CS		((unsigned int)1 << 30)	//OG30
 #define PIN_SD_DI		((unsigned int)1 << 31)	//OG31
#endif

#define SD_I_LO			(*R_PORT_G_DATA = port_g_data_shadow &= ~PIN_SD_DI)
#define SD_D_HI			(*R_PORT_G_DATA = port_g_data_shadow |=	PIN_SD_DI)
#define SD_E_HI			(*R_PORT_G_DATA = port_g_data_shadow |= PIN_SD_E)
#define SD_E_LO			(*R_PORT_G_DATA = port_g_data_shadow &= ~PIN_SD_E)
#define SD_CS_HI		(*R_PORT_G_DATA = port_g_data_shadow |= PIN_SD_CS)
#define SD_RST_LO		(*R_PORT_G_DATA = port_g_data_shadow &= ~PIN_SD_RST)
#define SD_WR_LO		(*R_PORT_G_DATA = port_g_data_shadow &= ~PIN_SD_RW)
#define SD_RD_HI		(*R_PORT_G_DATA = port_g_data_shadow |=	PIN_SD_RW)
#define SD_RST_HI		(*R_PORT_G_DATA = port_g_data_shadow |= PIN_SD_RST)
#define SD_CS_LO		(*R_PORT_G_DATA = port_g_data_shadow &= ~PIN_SD_CS)

#define BUS_MASK 		((unsigned int)0x0000ff00)

#define SD_CMD 			0
#define SD_DATA			1

#define SD_SET_LINE		0xb0
#define SD_SET_COL_LO		0x00
#define SD_SET_COL_HI		0x10
#define SD_SET_OFFSET		0xd3


static unsigned char sd1303_is_open = 0;

static void sd1303_send(unsigned int cmd, unsigned char type){
	if(type == SD_CMD){
		SD_I_LO;	
	} else {
		SD_D_HI;
	};
	SD_CS_LO;
	*R_PORT_G_DATA = port_g_data_shadow = (port_g_data_shadow & (~BUS_MASK)) | (cmd << 8);
	SD_CS_HI;
}

static void sd1303_set_pos(unsigned int x, unsigned int y){
	x += 2;
	sd1303_send(SD_SET_LINE | (y & 0x0f), SD_CMD);
	sd1303_send(SD_SET_COL_LO | (x & 0x0f), SD_CMD);
	sd1303_send(SD_SET_COL_HI | ((x & 0xf0) >> 4), SD_CMD);
}

static void sd1303_offset(unsigned char offset){
	sd1303_send(SD_SET_OFFSET, SD_CMD);
	sd1303_send(offset%64, SD_CMD);
	sd1303_send(0xe3, SD_CMD);
	sd1303_send(0xe3, SD_CMD);
	sd1303_send(0xe3, SD_CMD);
	sd1303_send(0xe3, SD_CMD);
	sd1303_send(0xe3, SD_CMD);
	sd1303_send(0xe3, SD_CMD);
	sd1303_send(0xe3, SD_CMD);
	sd1303_send(0xe3, SD_CMD);	
	if(offset%64 > 48){
		udelay(6700);
	} else if(offset%64 > 32){
		udelay(5000);
	} else if(offset%64 > 16){
		udelay(2250);	
	} else {
		udelay(900);
	}
}

static void sd1303_cls(void){
	int i, j;
	for(i = 0; i < 8; i++){
		sd1303_set_pos(0, i);
		for(j = 0; j < 128; j++){
			sd1303_send(0x00, SD_DATA);
		};
	}
	sd1303_offset(0);
}

static void sd1303_print(unsigned char *text, unsigned char x, unsigned char y){
	unsigned char i;
	sd1303_set_pos(0, y);
	unsigned char cols = 0;
	for(i = 0; i < x; i++){
		sd1303_send(0x00, SD_DATA);
		cols++;
	}
	while(*text){
		*text -= 0x20;
		if(*text > 127){
			*text = 0;
		};
		if(*text == 0){
			sd1303_send(0x00, SD_DATA);
			sd1303_send(0x00, SD_DATA);	
			cols += 2;
		} else {
			for(i = 0; i < 6; i++){
				if(font6_7[(*text * 6) + i] != 0){
					if(cols < 128){
						sd1303_send(font6_7[(*text * 6) + i] , SD_DATA);
						cols++;
					}
				}
			}
		}
		sd1303_send(0x00, SD_DATA);
		cols++;
		text++;
	}
	for(i = cols; i < 128; i++){
		sd1303_send(0x00, SD_DATA);
	}
	sd1303_send(0xe3, SD_CMD);
}

static void sd1303_print2(unsigned char *text1, unsigned char x1, unsigned char y, 
		unsigned char *text2, unsigned char x2, 
		unsigned char mask, unsigned char offset){
	unsigned char i;
	unsigned char data[128];
	unsigned char cols = 0;
	sd1303_offset(offset);
	
	for(i = 0; i < 128; i++){
		data[i] = 0x00;	
	}
	cols = x1;
	while(*text1){
		*text1 -= 0x20;
		if(*text1 > 127){
			*text1 = 0;
		}
		if(*text1 == 0){
			cols += 2;
		} else {
			for(i = 0; i < 6; i++){
				if(font6_7[(*text1 * 6) + i] != 0){
					if(cols < 128){
						data[cols] = font6_7[(*text1 * 6) + i] & mask;
						cols++;
					}
				}
			}
		}
		cols++;
		text1++;
	}
	
	cols = x2;
	while(*text2){
		*text2 -= 0x20;
		if(*text2 > 127){
			*text2 = 0;
		}
		if(*text2 == 0){
			cols += 2;
		} else {
			for(i = 0; i < 6; i++){
				if(font6_7[(*text2 * 6) + i] != 0){
					if(cols < 128){
						data[cols] |= font6_7[(*text2 * 6) + i] & ~mask;
						cols++;
					}
				}
			}
		}
		cols++;
		text2++;
	}
	
	sd1303_set_pos(0, y);
	for(i = 0; i < 128; i++){
		sd1303_send(data[i], SD_DATA);
	}
	sd1303_send(0xe3, SD_CMD);
}

static void sd1303_image(unsigned char *img){
	unsigned char x = img[0];
	unsigned char y = img[1];
	unsigned char cx = img[2];
	unsigned char cy = img[3];
	unsigned char s = img[4];
	unsigned char i, j;
	unsigned char *d = &img[5];
	sd1303_offset(0);
	for(i = 0; i < cy; i++){
		sd1303_set_pos(x, y + i);
		for(j = 0; j < cx; j++){
			sd1303_send(*d, SD_DATA);
			d++;
		}
	}
}

static void sd1303_test(void){
	int i, j;
	for(i = 0; i < 8; i++){
		sd1303_set_pos(i*16, i);
		for(j = 0; j < 16; j++){
			sd1303_send(0xff, SD_DATA);
		}
	}

	for(i = 0; i < 100; i++){
		sd1303_offset(i);
		udelay(100*1000);
	}
	
	unsigned char data[2048];
	memset(data, 0x55, 2048);
	data[0] = 0;
	data[1] = 0;
	data[2] = 64;
	data[3] = 4;
	sd1303_image(data);
	udelay(1000000);
	udelay(1000000);
	udelay(1000000);
	data[0] = 64;
	data[1] = 0;
	data[2] = 64;
	data[3] = 4;
	sd1303_cls();
	sd1303_image(data);
	udelay(1000000);
	udelay(1000000);
	udelay(1000000);
	data[0] = 0;
	data[1] = 4;
	data[2] = 64;
	data[3] = 4;
	sd1303_cls();
	sd1303_image(data);
	udelay(1000000);
	udelay(1000000);
	udelay(1000000);
	data[0] = 64;
	data[1] = 4;
	data[2] = 64;
	data[3] = 4;
	sd1303_cls();
	sd1303_image(data);
	udelay(1000000);
	udelay(1000000);
	udelay(1000000);
	sd1303_cls();

}

static void sd1303_init(void){
	unsigned char i = 0;
	unsigned char init_sequence[] = {
			0x00, 	// Set Lower Column Address 
			0x10, 	// Set Higher Column Address 
			0x40, 	// Set Display Start Line 
			0x81, 	// Set Contrast Control 
			0x80, 	// 0 ~ 127 
			0xA1, 	// [A0]:column address 1 is 
			0xC0, 	// oben / unten 
			0xA4, 	// Display on 
			0xA6, 	// Normal Display
			0xA8, 	// Set Multiplex Ratio 
			0x3f, 
			0xAD, 	// Set DC-DC 
			0x8A, 	// 8B=ON, 8A=Off 
			0xAF, 	// AF=ON , AE=OFF
			0xD3, 	// Set Display Offset
			0x00, 	// No offset 
			0xD5, 	// Set Clock Divide 
			0xd0, 	
			0xD8, 	// Set Area Color On or Off 
			0x00, 	// Mono Mode 
			0xDA, 	// Set Pins Hardware 
			0x12, 
			0xDB, 	// Set VCOMH 
			0x00, 
			0xD9, 	// Set VP 
			0x22, 
			0xff};
	
	genconfig_shadow |= IO_MASK(R_GEN_CONFIG,g8_15dir);	
        *R_GEN_CONFIG = genconfig_shadow;                  	
	
	while(init_sequence[i] != 0xff){
		sd1303_send(init_sequence[i], SD_CMD);
		i++;
	};
	sd1303_cls();
}

static void sd1303_reset(void){
	SD_CS_HI;
	SD_E_HI;
	SD_WR_LO;
	SD_D_HI;
	SD_RST_LO;
	udelay(10000);
	SD_RST_HI;
}

#define IOCTL_SD_CLS	 		0x01
#define IOCTL_SD_INIT			0x02
#define IOCTL_SD_TEXT			0x03
#define IOCTL_SD_TEXT2			0x04
#define IOCTL_SD_SCROLL			0x05
#define IOCTL_SD_TEST			0x06
#define IOCTL_SD_IMAGE			0x07

#define IMAGE_SIZE			((128 * 8) + 5)

static int module_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg){
	unsigned char text[513];
	unsigned char image[IMAGE_SIZE];
	switch (cmd) {
		case IOCTL_SD_CLS:
			sd1303_cls();
			break;
		case IOCTL_SD_INIT:
			sd1303_init();
			break;
		case IOCTL_SD_TEST:
			sd1303_test();	
			break;
		case IOCTL_SD_SCROLL:
			sd1303_offset(arg);	
			break;
		case IOCTL_SD_TEXT:
			copy_from_user(text, (unsigned char*)arg, 256);
			sd1303_print(&text[2], text[0], text[1]);
			break;
		case IOCTL_SD_TEXT2:
			copy_from_user(text, (unsigned char*)arg, 512);
			sd1303_print2(&text[5], text[0], text[1], &text[260], text[2], text[3], text[4]);
			break;
		case IOCTL_SD_IMAGE:
			copy_from_user(image, (unsigned char*)arg, IMAGE_SIZE);
			sd1303_image(image);
			break;
		default:
			printk("%s : unknown ioctl\n", DRIVER_NAME);
			//errno = EINVAL;
			return -1;
	}
	return 0;
}

static int module_open(struct inode *inode, struct file *file){
	unsigned int dev_minor = MINOR(inode->i_rdev);
	if(dev_minor !=  0){
		printk("%s : trying to access unknown minor device -> %d\n", DRIVER_NAME, dev_minor);
		return -ENODEV;
	}
	if(sd1303_is_open) {
		printk("%s : Device with minor ID %d already in use\n", DRIVER_NAME, dev_minor);
		return -EBUSY;
	}
	sd1303_is_open = 1;
	printk("%s : Minor %d has been opened\n", DRIVER_NAME, dev_minor);
	return 0;
}

static int module_close(struct inode * inode, struct file * file){
	unsigned int dev_minor = MINOR(inode->i_rdev);
	sd1303_is_open = 0;
	printk("%s : Minor %d has been closed\n", DRIVER_NAME, dev_minor);
	return 0;
}

struct file_operations ssd1303_module_fops = {
        ioctl:         module_ioctl,
        open:          module_open,
        release:       module_close
};

static int __init mod_init(void){
	printk("%s : starting %s driver\n", DRIVER_NAME, DRIVER_NAME);
	sd1303_reset();
	sd1303_init();
	if(register_chrdev(DEV_MAJOR, DEV_NAME, &ssd1303_module_fops)) {
		printk("%s : Error whilst opening %s (%d)\n", DRIVER_NAME, DEV_NAME, DEV_MAJOR);
		return -ENODEV;
	};
	printk("%s : Device %s registered for major ID %d\n", DRIVER_NAME, DEV_NAME, DEV_MAJOR);
	return 0;
}

static void __exit mod_exit(void){
	printk("%s : Cleanup\n", DRIVER_NAME);
	unregister_chrdev(DEV_MAJOR, DEV_NAME);
}

module_init (mod_init);
module_exit (mod_exit);


